<?php
/**
 * Created by PhpStorm.
 * User: Mr.Li <lhb2002@qq.com>
 * Date: 2020/1/9
 * Time: 14:08
 */

namespace LHB\ApiEarlyWarning;


class ApiEarlyWarning
{
    const KEY_PREFIX = 'PlatformState:';
    const KEY_SUFFIX_FAILURE = '_failure';
    const KEY_SUFFIX_SUCCESS = '_success';
    const KEY_SUFFIX_WAIT = '_wait';

    const EVENT_FAILURE_WARNING = 'failure_warning';
    const EVENT_WAIT_WARNING = 'wait_warning';
    /**
     * @var string 服务名称
     */
    private $service;

    /**
     * @var \Redis 缓存数据
     */
    private $redis;

    /**
     * @var callable[]
     */
    private $subscribers = [];

    private $config = [
        'timeWindow' => 30 ,
        'failureRate' => 0.01 ,
        'min_failure_warning' => 1 ,
        'waitRate' => 0.1 ,
        'min_wait_warning' => 1
    ];

    public function __construct(
        \Redis $redis ,
        string $service ,
        array $config = []
    )
    {
        $this->redis = $redis;
        $this->service = $service;
        $this->config = array_merge($this->config,$config);
    }


    /**
     * @param callable $callable
     * @return void
     */
    public function subscribe(callable $callable)
    {
        $this->subscribers[] = $callable;
    }

    /**
     * @param string $event
     * @param string $service
     * @param string $message
     * @return void
     */
    private function notify($event,  $message)
    {
        foreach ($this->subscribers as $s) {
            call_user_func_array($s, [$event, $this->service, $message]);
        }
    }

    /**
     * 获取服务键名
     * @return string
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    private function key( )
    {
        return self::KEY_PREFIX . $this->service ;
    }


    /**
     * 获取失败服务键名
     * @return string
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    private function failureKey( )
    {
        return $this->key( ) . self::KEY_SUFFIX_FAILURE;
    }

    /**
     * 获取成功服务键名
     * @return string
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    private function successKey( )
    {
        return $this->key( ) . self::KEY_SUFFIX_SUCCESS;
    }

    /**
     * 获取等待处理的服务键名
     * @return string
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 15:04
     */
    private function waitKey( )
    {
        return $this->key( ) . self::KEY_SUFFIX_WAIT;
    }

    /**
     * 记录数据
     * @param string $key
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    private function record( string $key , string  $message ){
        $t = microtime(true);
        $this->redis->zAdd( $key , $t , $message );
    }

    /**
     * 获取未过期时间片段的数据条数
     * @param string $key
     * @return int
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    private function getCount(string $key )
    {
        $expires = microtime(true) - $this->config['timeWindow'];

        if ($this->redis->zRemRangeByScore($key, '-inf', $expires) === false) {
            //throw new \RuntimeException('Failed to remove expired elements. service: ' . $key);
        }

        $r = $this->redis->zCard($key);

        if ($r === false) {
            return 0;
            //throw new \RuntimeException('Failed to load cardinality. service: ' . $key);
        }

        return $r;
    }

    /**
     * 获取成功数量
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    public function getSuccessCount( )
    {
        return $this->getCount( $this->successKey() );
    }

    /**
     * 获取失败数量
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    public function getFailureCount(  )
    {
        return $this->getCount( $this->failureKey() );
    }


    /**
     * 获取所有待执行的数据
     * @return int
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 15:08
     */
    public function getWaitCount(  )
    {
        $key = $this->waitKey();
        $r = $this->redis->zCard( $key );

        if ($r === false) {
            throw new \RuntimeException('Failed to load cardinality. service: ' . $key );
        }

        return $r;
    }

    /**
     * 移除已处理的待执行任务
     * @param string $message
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 15:07
     */
    public function removeWait(string $message )
    {
        $this->redis->zRem( $this->waitKey() , $message );
    }

    /**
     * 获取最早的一条待执行的任务，便于超时进行提醒
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 15:09
     */
    public function getFirstWait()
    {
        $first =  $this->redis->zRange( $this->waitKey() , 0 , 0, true );
        if(empty($first)){
            return [ null , null ];
        }
        else{
            return [ key( $first ) , current( $first ) ];
        }
    }

    /**
     * 记录成功
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    public function success( string $message )
    {
        $this->record( $this->successKey() , $message );
        //删除待执行的任务，标记任务为已完成
        $this->removeWait( $message );
        $this->check();
    }

    /**
     * 记录失败
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    public function failure(  string $message  )
    {
        $this->record( $this->failureKey() , $message );
        //删除待执行的任务，标记任务为已完成
        $this->removeWait( $message );

        $this->check();
    }

    /**
     * 记录等待处理
     * @param string $message
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/10
     * Time: 9:57
     */
    public function wait (string $message  )
    {
        $this->record( $this->waitKey() , $message );
        $this->check();
    }


    /**
     * 检查结果
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/10
     * Time: 9:57
     */
    public function check( )
    {
        $successCount = $this->getSuccessCount();
        $failureCount = $this->getFailureCount();
        $waitCount = $this->getWaitCount();

        $this->checkFailure( $successCount , $failureCount );
        $this->checkWait( $successCount , $failureCount , $waitCount );
    }

    /**
     * 获取失败率
     * @param $successCount
     * @param $failureCount
     * @return float|int
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/10
     * Time: 10:24
     */
    public function getFailureRate( int $successCount , int $failureCount  )
    {
        if( ($successCount + $failureCount ) == 0){
            return 0;
        }
        else
        {
            $rate = $failureCount / ( $successCount + $failureCount ) * 100;
            return round( $rate,2 );
        }
    }
    /**
     * 检查是否满足失败通知
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:37
     */
    public function checkFailure( int $successCount , int  $failureCount )
    {
        if($failureCount==0){
            return false;
        }

        //计算失败率
        $rate = $this->getFailureRate( $successCount , $failureCount );

        //判断失败率是否大于预设值或失败数量大于预设最小预警值
        if(
            (
                $rate > $this->config['failureRate']
                && $failureCount >= $this->config['min_failure_warning']
            )
            || $failureCount >= $this->config['min_failure_warning']
        ){
            $this->notify(self::EVENT_FAILURE_WARNING , [ $failureCount ,$rate ] );
            return true;
        }

        return false;
    }

    /**
     * 获取等待率
     * @param int $successCount
     * @param int $failureCount
     * @param int $waitCount
     * @return float|int 0-100
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/10
     * Time: 10:28
     */
    public function getWaitRate(int $successCount , int $failureCount , int $waitCount )
    {
        if( ($waitCount + $successCount + $failureCount) == 0){
            return 0;
        }
        else
        {
            $rate = $waitCount / ( $waitCount + $successCount + $failureCount ) * 100;
            return round( $rate,2 );
        }
    }
    /**
     * 检查数据等待数量
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 15:16
     */
    public function checkWait( int $successCount , int $failureCount ,int $waitCount )
    {
        if($waitCount==0){
            return false;
        }
        $rate = $this->getWaitRate( $successCount , $failureCount , $waitCount  );
        //判断失败率是否大于预设值或失败数量大于预设最小预警值
        if(
            (
                $rate > $this->config['waitRate']
                && $waitCount >= $this->config['min_wait_warning']
            )
            || $waitCount >= $this->config['min_wait_warning']
        ){
            $this->notify(self::EVENT_WAIT_WARNING , [ $waitCount , $rate ]);
            return true;
        }

        return false;
    }


    /**
     * 重置当前状态
     * User: Mr.Li <lhb2002@qq.com>
     * Date: 2020/1/9
     * Time: 14:53
     */
    public function reset( ){
        $keys = [
            $this->key(),
            $this->successKey(),
            $this->failureKey(),
            $this->waitKey()
        ];
        $this->redis->del( $keys );
    }


}